﻿//-------------------------------------------------------------------
// VOString implementation file
//-------------------------------------------------------------------
//
// Copyright ?000- 02 Virtual Office Systems Incorporated
// All Rights Reserved
//
// This code may be used in compiled form in any way you desire. This
// file may be redistributed unmodified by any means PROVIDING it is
// not sold for profit without the authors written consent, and
// providing that this notice and the authors name is included.
//
// This code can be compiled, modified and distributed freely, providing
// that this copyright information remains intact in the distribution.
//
// This code may be compiled in original or modified form in any private
// or commercial application.
//
// This file is provided "as is" with no expressed or implied warranty.
// The author accepts no liability for any damage, in any form, caused
// by this code. Use it at your own risk.
//-------------------------------------------------------------------

#include "stdafx.h"
#include "VOString.h"


//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CVOString::CVOString(LPCTSTR pcszValue)
		: m_pBuffer(NULL),
		m_dwLength(0),
		m_dwBufferSize(0)
{
	*this = pcszValue;
}

// Copy Constructor
CVOString::CVOString(const CVOString &rSrc)
		: m_pBuffer(NULL),
		m_dwLength(0),
		m_dwBufferSize(0)
{
	*this = rSrc.m_pBuffer;
}

CVOString::~CVOString()
{
	if (m_pBuffer)
	{
		delete [] m_pBuffer;
	}
}

void CVOString::operator = (const CVOString& rSrc)
{
	if ( &rSrc != this )
	{
		*this = rSrc.m_pBuffer;
	}
}

void CVOString::operator = (LPCTSTR pcszValue)
{
	if (pcszValue == NULL)
	{
		m_dwLength = 0;

		if (m_pBuffer)
		{
			delete [] m_pBuffer;
			m_pBuffer = NULL;
		}
	}
	else
	{
		m_dwLength = _tcslen(pcszValue);

		SetMinBufferSize(m_dwLength);

		if (m_dwLength == 0)
		{
			m_pBuffer[0] = TCHAR(0);
		}
		else
		{
			_tcscpy(m_pBuffer, pcszValue);
		}
	}
}

BOOL CVOString::operator == (LPCTSTR pcszValue) const
{
	if (!m_pBuffer)
	{
		if (!pcszValue)
		{
			return TRUE;
		}

		return (_tcsicmp(pcszValue, TEXT("")) == 0);
	}

	return(_tcsicmp(pcszValue, m_pBuffer) == 0);
}

BOOL CVOString::operator == (const CVOString& rCompare) const
{
	if (!m_pBuffer)
	{
		if (rCompare.GetLength() == 0)
		{
			return TRUE;
		}
		else
		{
			return FALSE;
		}
	}

	if (!rCompare.m_pBuffer)
	{
		return FALSE;
	}

	return(_tcsicmp(rCompare.m_pBuffer, m_pBuffer) == 0);
}

LPCTSTR CVOString::operator += (LPCTSTR pcszAppend)
{
	if (m_pBuffer)
	{
		SetMinBufferSize(GetLength() + _tcslen(pcszAppend));
		_tcscat(m_pBuffer, pcszAppend);
		m_dwLength = _tcslen(m_pBuffer);
	}
	else
	{
		*this = pcszAppend;
	}

	return *this;
}

LPCTSTR CVOString::operator += (TCHAR chAppend)
{
	SetMinBufferSize(m_dwLength + 2);
	m_dwLength++;
	m_pBuffer[m_dwLength] = 0;
	m_pBuffer[m_dwLength - 1] = chAppend;

	return *this;
}

CVOString CVOString::operator + (LPCTSTR pcszAppend) const
{
	CVOString strReturn(*this);

	strReturn += pcszAppend;

	return strReturn;
}

BOOL CVOString::operator != (LPCTSTR pcszValue) const
{
	return operator ==(pcszValue) == 0;
}

BOOL CVOString::operator != (const CVOString& rValue) const
{
	return operator ==(rValue) == 0;
}

CVOString::operator LPCTSTR() const
{
	return (LPCTSTR)m_pBuffer;
}

DWORD CVOString::GetLength() const
{
	return m_dwLength;
}

BOOL CVOString::SetMinBufferSize(DWORD dwChars)
{
#ifdef _DEBUG
	if (dwChars > 65536)
	{
		DebugBreak();	// This is just a precaution to protect against memory leaks.  Large strings are fine.
	}
#endif
	if ( (m_dwBufferSize < dwChars + 1) || (m_pBuffer == NULL) )
	{
		TCHAR*	pNewBuffer = NULL;
		DWORD	dwNewBufferSize = dwChars + 16;

		pNewBuffer = new TCHAR[dwNewBufferSize];

		memset(pNewBuffer, 0, sizeof(TCHAR) * dwNewBufferSize);

		if ( m_pBuffer )
		{
			memmove(pNewBuffer, m_pBuffer, m_dwBufferSize * sizeof(TCHAR));
			delete [] m_pBuffer;
		}

		m_pBuffer = pNewBuffer;

		m_dwBufferSize = dwNewBufferSize;
	}

	return TRUE;
}

TCHAR CVOString::GetAt(DWORD dwOffset) const
{
	if (dwOffset > m_dwLength - 1)
	{
		return (TCHAR) 0;
	}

	return m_pBuffer[dwOffset];
}

CVOString CVOString::Left(DWORD dwCount) const
{
	if (dwCount >= m_dwLength)
	{
		return CVOString(*this);
	}

	LPTSTR	pszTemp = new TCHAR[dwCount + 1];

	memset(pszTemp, 0, sizeof(TCHAR) * (dwCount + 1));
	memmove(pszTemp, m_pBuffer, sizeof(TCHAR) * dwCount);

	CVOString strValue(pszTemp);

	delete [] pszTemp;

	return strValue;
}

CVOString CVOString::Mid(DWORD dwOffset, int nLength) const
{
	if ( dwOffset > m_dwLength )
	{
		return CVOString(TEXT(""));
	}

	if (nLength == -1)
	{
		nLength = (int)(m_dwLength - dwOffset);
	}
	else if (dwOffset + (DWORD)nLength > m_dwLength)
	{
		nLength = (int)(m_dwLength - dwOffset);
	}

	LPTSTR	pszTemp = new TCHAR[(size_t)(nLength + 1)];

	memset(pszTemp, 0, sizeof(TCHAR) * ((size_t)nLength + 1));
	memmove(pszTemp, m_pBuffer + dwOffset, sizeof(TCHAR) * (size_t)nLength);

	CVOString strValue(pszTemp);

	delete [] pszTemp;

	return strValue;
}

LPTSTR CVOString::GetBuffer(DWORD dwMinSize)
{
	SetMinBufferSize(dwMinSize + 1);

	return m_pBuffer;
}

int CVOString::Format(LPCTSTR pcszFormat, ...)
{
	va_list vl;

	va_start( vl, pcszFormat);

	::wvsprintf(GetBuffer(256), pcszFormat, vl);
	ReleaseBuffer();

	va_end(vl);

	return (int)(GetLength());
}

int CVOString::Find(LPCTSTR pcszValue, int nStartingOffset) const
{
	LPTSTR pszSubstring;

	if (nStartingOffset < 0)
	{
		nStartingOffset = 0;
	}

	pszSubstring = _tcsstr(m_pBuffer + nStartingOffset, pcszValue);

	if (!pszSubstring)
	{
		return -1;
	}

	return (pszSubstring - m_pBuffer);
}

int CVOString::Find(TCHAR chValue, int nStartingOffset) const
{
	TCHAR pcszValue[2];

	pcszValue[0] = chValue;
	pcszValue[1] = 0;

	return Find(pcszValue, nStartingOffset);
}

int CVOString::ReverseFind(LPCTSTR pcszSubstring) const
{
	int nOffset = -1, nNextOffset = 0;

	while ((nNextOffset = Find(pcszSubstring, nNextOffset)) != -1)
	{
		nOffset = nNextOffset++;
	}

	return nOffset;
}

DWORD CVOString::ReleaseBuffer(int nChars)
{
	if ((m_pBuffer == NULL) || (m_dwBufferSize == 0))
	{
		return 0;
	}

	if (nChars == -1)
	{
		m_dwLength = _tcslen(m_pBuffer);
	}
	else
	{
		m_dwLength = (DWORD) nChars;

		if (m_dwLength > m_dwBufferSize)
		{
			m_dwLength = m_dwBufferSize;
		}

		m_pBuffer[m_dwLength] = 0;
	}

	return m_dwLength;
}

const CVOString& CVOString::TrimRight()
{
	while (GetLength() && GetAt(GetLength() - 1) == TCHAR(' '))
	{
		*this = Left(GetLength() - 1);
	}

	return *this;
}

const CVOString& CVOString::TrimLeft()
{
	while (GetLength() && GetAt(0) == TCHAR(' '))
	{
		*this = Mid(1);
	}

	return *this;
}

const CVOString& CVOString::Trim()
{
	TrimLeft();

	return TrimRight();
}

CVOString CVOString::Right(int nChars) const
{
	if ((int)GetLength() < nChars)
	{
		return *this;
	}

	return Mid(GetLength() - (DWORD)nChars);
}

void CVOString::MakeMixedCase()
{
	BOOL fFirstWord = TRUE;

	for (int i = 0; i < (int)GetLength(); ++i)
	{
		if ( (m_pBuffer[i] >= TCHAR('a') && m_pBuffer[i] <= TCHAR('z')) ||
			(m_pBuffer[i] >= TCHAR('A') && m_pBuffer[i] <= TCHAR('Z')) )	// Start of Word
		{
			int nWordEnd = i;

			while (	(m_pBuffer[nWordEnd] >= TCHAR('a') && m_pBuffer[nWordEnd] <= TCHAR('z')) ||
				(m_pBuffer[nWordEnd] >= TCHAR('A') && m_pBuffer[nWordEnd] <= TCHAR('Z')) )
			{
				nWordEnd++;
			}

			CVOString strWord;

			strWord = Mid((DWORD)i, nWordEnd - i);
			strWord.MakeLower();

			BOOL fCapitalize = FALSE;

			if (fFirstWord)
			{
				fCapitalize = TRUE;
				fFirstWord = FALSE;
			}
			else
			{
				fCapitalize = TRUE;

				if (strWord == TEXT("a"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("an"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("the"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("and"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("but"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("or"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("nor"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("at"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("by"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("for"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("from"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("in"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("into"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("of"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("off"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("on"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("onto"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("out"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("to"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("up"))
				{
					fCapitalize = FALSE;
				}
				else if (strWord == TEXT("with "))
				{
					fCapitalize = FALSE;
				}
			}

			if (fCapitalize)
			{
				strWord.m_pBuffer[0] = strWord.m_pBuffer[0] - (TCHAR('a') - TCHAR('A'));
			}

			memmove(m_pBuffer + i, strWord.m_pBuffer, strWord.GetLength() * sizeof(TCHAR));
			i = nWordEnd - 1;
		}
		else if (m_pBuffer[i] == TCHAR('.') || m_pBuffer[i] == TCHAR('?') || m_pBuffer[i] == TCHAR('!'))
		{
			fFirstWord = TRUE;
		}
	}
}

void CVOString::MakeLower()
{
	for (int i = 0; i < (int)GetLength(); i++)
	{
		if (m_pBuffer[i] >= TCHAR('A') && m_pBuffer[i] <= TCHAR('Z'))
		{
			m_pBuffer[i] = m_pBuffer[i] + (TCHAR('a') - TCHAR('A'));
		}
	}
}

void CVOString::MakeUpper()
{
	for (int i = 0; i < (int)GetLength(); i++)
	{
		if (m_pBuffer[i] >= TCHAR('a') && m_pBuffer[i] <= TCHAR('z'))
		{
			m_pBuffer[i] = m_pBuffer[i] - (TCHAR('a') - TCHAR('A'));
		}
	}
}

int CVOString::CompareNoCase(LPCTSTR pcszValue) const
{
	if (!m_pBuffer && !pcszValue)
	{
		return 0;
	}

	if (!pcszValue)
	{
		return 1;
	}

	if (!m_pBuffer)
	{
		return -1;
	}

	return _tcsicmp(m_pBuffer, pcszValue);
}

int CVOString::FindOneOf(LPCTSTR pcszDelims) const
{
	if (m_pBuffer == NULL || m_dwBufferSize == 0)
	{
		return -1;
	}

	if (!pcszDelims)
	{
		return -1;
	}

	int nDelim = (int)(_tcscspn((LPCTSTR)m_pBuffer, pcszDelims));

	if (nDelim == (int)GetLength())
	{
		return -1;
	}

	return nDelim;
}

DWORD CVOString::Replace(LPCTSTR pcszSearchFor, LPCTSTR pcszReplaceWith)
{
	CVOString	strSearchFor(pcszSearchFor);
	CVOString	strReplaceWith(pcszReplaceWith);
	CVOString	strRemain;

	if (strReplaceWith.Find(strSearchFor) != -1)
	{
		return 0;
	}	// Would be infinite loop

	DWORD	dwCount = 0;
	int		nMatch = -1;

	while ( (nMatch = Find(strSearchFor, nMatch + 1)) != -1 )
	{
		strRemain = Mid((DWORD)nMatch + strSearchFor.GetLength());
		*this = Left((DWORD)nMatch);
		*this += strReplaceWith;
		*this += strRemain;

		nMatch += (int)(strReplaceWith.GetLength() - 1);
		dwCount++;
	}

	return dwCount;
}

#define SZ_QUOTE			TEXT("\"")
#define SZ_QUOTE_ENCODED	TEXT("%22")

CVOString CVOString::AddQuotes(LPCTSTR pcszValue, TCHAR chQuote)
{
	CVOString strValue(pcszValue);

	strValue.Replace(SZ_QUOTE, SZ_QUOTE_ENCODED);

	CVOString strNewValue;

	strNewValue += chQuote;
	strNewValue += strValue;
	strNewValue += chQuote;

	return strNewValue;
}

CVOString CVOString::RemoveQuotes(LPCTSTR pcszValue)
{
	CVOString strValue(pcszValue);

	if (strValue.GetLength() < 2)
	{
		return strValue;
	}

	if (	(strValue.GetAt(0) == CH_QUOTE) &&
		(strValue.GetAt(strValue.GetLength() - 1) == CH_QUOTE) )
	{
		strValue = strValue.Mid(1, (int)(strValue.GetLength() - 2));
	}

	strValue.Replace(SZ_QUOTE_ENCODED, SZ_QUOTE);

	return strValue;
}

CVOString CVOString::EncodeHex(LPCTSTR pcszValue)
{
	if (!pcszValue)
	{
		return CVOString(TEXT(""));
	}

	static TCHAR hexChar[] =
	{
		TCHAR('0'), TCHAR('1'), TCHAR('2'), TCHAR('3'),
		TCHAR('4'), TCHAR('5'), TCHAR('6'), TCHAR('7'),
		TCHAR('8'), TCHAR('9'), TCHAR('A'), TCHAR('B'),
		TCHAR('C'), TCHAR('D'), TCHAR('E'), TCHAR('F')
	};

	CVOString strValue;

	LPCTSTR pcszCur = pcszValue;

	unsigned char chCur;

	while (*pcszCur)
	{
		chCur = (unsigned char)(*pcszCur);
		strValue += hexChar[chCur >> 4];
		strValue += hexChar[chCur & 0xF];
		pcszCur++;
	}

	return strValue;
}

#pragma warning(disable:4244)
CVOString CVOString::DecodeHex(LPCTSTR pcszValue)
{
	CVOString strValue(pcszValue);
	CVOString strDecoded;

	strValue.MakeUpper();

	if ( strValue.GetLength() % 2 )	// Uneven hex pairs
	{
		return CVOString(TEXT(""));
	}

	UCHAR	ch;
	TCHAR	nibHigh, nibLow;

	for (int nChar = 0; nChar < (int)strValue.GetLength() / 2; nChar++)
	{
		ch = 0;

		nibHigh = strValue.GetAt((DWORD)(nChar * 2));
		nibLow = strValue.GetAt((DWORD)(nChar * 2 + 1));

		switch (nibHigh)
		{
		case TCHAR('0'):
		case TCHAR('1'):
		case TCHAR('2'):
		case TCHAR('3'):
		case TCHAR('4'):
		case TCHAR('5'):
		case TCHAR('6'):
		case TCHAR('7'):
		case TCHAR('8'):
		case TCHAR('9'):
			ch += (unsigned char)(nibHigh - TCHAR('0'));
			break;

		case TCHAR('A'):
		case TCHAR('B'):
		case TCHAR('C'):
		case TCHAR('D'):
		case TCHAR('E'):
		case TCHAR('F'):
			ch += (unsigned char)((nibHigh - TCHAR('A')) + 10);
			break;

		default:
			break;
		}

		ch = ch << 4;

		switch (nibLow)
		{
		case TCHAR('0'):
		case TCHAR('1'):
		case TCHAR('2'):
		case TCHAR('3'):
		case TCHAR('4'):
		case TCHAR('5'):
		case TCHAR('6'):
		case TCHAR('7'):
		case TCHAR('8'):
		case TCHAR('9'):
			ch += (unsigned char)(nibLow - TCHAR('0'));
			break;

		case TCHAR('A'):
		case TCHAR('B'):
		case TCHAR('C'):
		case TCHAR('D'):
		case TCHAR('E'):
		case TCHAR('F'):
			ch += (unsigned char)((nibLow - TCHAR('A')) + 10);
			break;

		default:
			break;
		}

		strDecoded += (TCHAR)ch;
	}

	return strDecoded;
}
#pragma warning(default:4244)

BOOL CVOString::PopElement(CVOString& strValue, CVOString& strSource, const CVOString& strDelim)
{
	BOOL	fDoubleQuote = FALSE;
	BOOL	fSingleQuote = FALSE;
	int		nOffset = 0;

	strSource.TrimLeft();

	if (strSource.GetLength() == 0)
	{
		return FALSE;
	}

	if (strSource.GetAt((DWORD)nOffset) == TCHAR('\"'))	// Double quoted element
	{
		fDoubleQuote = TRUE;
		++nOffset;
	}
	else if (strSource.GetAt((DWORD)nOffset) == TCHAR('\''))	// Single quoted element
	{
		fSingleQuote = TRUE;
		++nOffset;
	}

	while ( nOffset < (int)strSource.GetLength() )
	{
		if ( (fSingleQuote && (strSource.GetAt((DWORD)nOffset) == TCHAR('\''))) ||
			(fDoubleQuote && (strSource.GetAt((DWORD)nOffset) == TCHAR('\"'))) )
		{
			strValue = strSource.Mid(1, nOffset - 1);
			strSource = strSource.Mid((DWORD)(nOffset + 2));
			strValue.TrimLeft();
			strValue.TrimRight();
			return TRUE;
		}
		else if ((!fSingleQuote && !fDoubleQuote) && (strDelim.Find(strSource.GetAt((DWORD)nOffset)) != -1) )
		{
			strValue = strSource.Left((DWORD)nOffset);
			strSource = strSource.Mid((DWORD)(nOffset + 1));
			strValue.TrimLeft();
			strValue.TrimRight();
			return TRUE;
		}

		nOffset++;
	}

	// Reached the end of the string.  Return remainder
	strValue = strSource;
	strSource.Empty();
	strValue.TrimLeft();
	strValue.TrimRight();

	return TRUE;
}

BOOL CVOString::StartsWith(LPCTSTR pcszValue, BOOL fMatchCase) const
{
	if (!pcszValue)
	{
		return FALSE;
	}

	CVOString strValue(pcszValue);

	if ( fMatchCase )
	{
		return Left(strValue.GetLength()) == strValue;
	}

	return (Left(strValue.GetLength()).CompareNoCase(strValue) == 0);
}

BOOL CVOString::EndsWith(LPCTSTR pcszValue, BOOL fMatchCase) const
{
	if (!pcszValue)
	{
		return FALSE;
	}

	CVOString strValue(pcszValue);

	if (fMatchCase)
	{
		return Right((int)(strValue.GetLength())) == strValue;
	}

	return (Right((int)(strValue.GetLength())).CompareNoCase(strValue) == 0);
}

BOOL CVOString::Contains(LPCTSTR pcszValue, BOOL fMatchCase) const
{
	if ( !pcszValue )
	{
		return FALSE;
	}

	CVOString strValue(pcszValue);

	if ( fMatchCase )
	{
		return (Find(pcszValue) != -1);
	}

	// Case insensitive search
	static CVOString strLast;
	static CVOString strUpper;

	if (strLast != *this)	// The UC buffer is stored because it is not uncommon to call this repeatedly on the same string
	{
		strLast = *this;
		strUpper = *this;
		strUpper.MakeUpper();
	}

	strValue.MakeUpper();

	return (strUpper.Find(strValue) != -1);
}

#define LOAD_SIZE 256

BOOL CVOString::LoadString(HINSTANCE hInstance, DWORD dwResourceID)
{
	TCHAR cLoad[LOAD_SIZE];
	int nBytesCopied = ::LoadString(hInstance, dwResourceID, cLoad, LOAD_SIZE);

	LPTSTR lpThis = GetBuffer((DWORD)nBytesCopied);

	_tcscpy_s(lpThis, (DWORD)(nBytesCopied + 1), cLoad);

	ReleaseBuffer();

	return (nBytesCopied > 0);
}

void CVOString::Empty()
{
	m_dwLength = 0;
	m_dwBufferSize = 0;

	if ( m_pBuffer )
	{
		delete [] m_pBuffer;
		m_pBuffer = NULL;
	}
}

BOOL CVOString::IsEmpty() const
{
	if ( m_pBuffer && 0 < m_dwLength )
	{
		return FALSE;
	}
	else
	{
		return TRUE;
	}
}
